/*
    The NeonX Library

    Copyright (c) 2013 darkcat

    Code covered by the MIT License
*/

#pragma once

// nx...
#include "config/general.h"

#if defined(NX_OS_WIN)
#include <process.h>
#include <tlhelp32.h>
#elif defined(NX_OS_LINUX)
#include <pthread.h>
#endif

//////////////////////////////////////////////////////////////////////////
NX_BEG
//////////////////////////////////////////////////////////////////////////

namespace thread
{
    /*
        Basic functions
    */

#if defined(NX_OS_WIN)
    typedef DWORD       id_t;
    typedef HANDLE      handle_t;
#elif defined(NX_OS_LINUX)
    typedef pthread_t   id_t;
    typedef pthread_t   handle_t;
#endif

#if defined(NX_OS_WIN)
#   define NX_THREAD_PROC(name, ...) unsigned (__stdcall name)(void* __VA_ARGS__)
#elif defined(NX_OS_LINUX)
#   define NX_THREAD_PROC(name, ...) void* (name)(void* __VA_ARGS__)
#endif
    typedef NX_THREAD_PROC(*proc_t);

    inline handle_t create(proc_t proc, pvoid arg = NX_NULL)
    {
#if defined(NX_OS_WIN)
        return (handle_t)_beginthreadex(NULL, 0, proc, arg, 0, NULL);
#elif defined(NX_OS_LINUX)
        handle_t hd = 0;
        int err = pthread_create(&hd, NULL, proc, arg);
        return (err == 0) ? hd : 0;
#endif
    }

    inline handle_t current_handle(void)
    {
#if defined(NX_OS_WIN)
        return ::GetCurrentThread();
#elif defined(NX_OS_LINUX)
        return pthread_self();
#endif
    }

    inline id_t current_id(void)
    {
#if defined(NX_OS_WIN)
        return ::GetCurrentThreadId();
#elif defined(NX_OS_LINUX)
        return pthread_self();
#endif
    }

    inline handle_t id2handle(id_t id)
    {
#if defined(NX_OS_WIN)

#   if defined(NX_OS_WINCE)
        return (handle_t)id; /* T.B.D */
#   else
        return ::OpenThread(THREAD_ALL_ACCESS, FALSE, id);
#   endif

#elif defined(NX_OS_LINUX)
        return (handle_t)id;
#endif
    }

    inline id_t handle2id(handle_t hd)
    {
#if defined(NX_OS_WIN)

#   if defined(NX_OS_WINCE)
        return (id_t)hd; /* T.B.D */
#   elif (WINVER >= 0x0502)
        return ::GetThreadId(hd);
#   else
        if (hd == current_handle()) return current_id();
        HANDLE hThreadSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
        if (hThreadSnap == INVALID_HANDLE_VALUE) return 0;
        THREADENTRY32 te32 = {0};
        te32.dwSize = sizeof(te32);
        if (::Thread32First(hThreadSnap, &te32) == FALSE) return 0;
        DWORD dwProcessId = ::GetCurrentProcessId();
        while(::Thread32Next(hThreadSnap, &te32))
        {
            if (te32.th32OwnerProcessID == dwProcessId)
            {
                HANDLE hTempThread = id2handle(te32.th32ThreadID);
                if (hTempThread == hd)
                {
                    ::CloseHandle(hTempThread);
                    ::CloseHandle(hThreadSnap);
                    return te32.th32ThreadID;
                }
                else
                    ::CloseHandle(hTempThread);
            }
        }
        return 0;
#   endif

#elif defined(NX_OS_LINUX)
        return (id_t)hd;
#endif
    }

    inline bool cancel(handle_t hd)
    {
#if defined(NX_OS_WIN)
        if (::TerminateThread(hd, 0))
        {
            ::CloseHandle(hd);
            return true;
        }
        return false;
#elif defined(NX_OS_LINUX)
        return pthread_cancel(hd);
#endif
    }
    
    inline bool join(handle_t hd)
    {
#if defined(NX_OS_WIN)
        if (::WaitForSingleObject(hd, INFINITE) == WAIT_OBJECT_0)
        {
            ::CloseHandle(hd);
            return true;
        }
        return false;
#elif defined(NX_OS_LINUX)
        int err = pthread_join(hd, NULL);
        return (err == 0);
#endif
    }

    inline void exit(void)
    {
#if defined(NX_OS_WIN)
        _endthread();
#elif defined(NX_OS_LINUX)
        pthread_exit(0);
#endif
    }

    inline void yield(void)
    {
#if defined(NX_OS_WIN)

#   if defined(NX_OS_WINCE)
        ::Sleep(5);
#   else
        ::SwitchToThread();
#   endif

#elif defined(NX_OS_LINUX)
        pthread_yield();
#endif
    }

    /*
        Mutex
    */

#if defined(NX_OS_WIN)
    typedef CRITICAL_SECTION    mutex_t;
#elif defined(NX_OS_LINUX)
    typedef pthread_mutex_t     mutex_t;
#endif

    inline bool mutex_init(mutex_t& mx)
    {
#if defined(NX_OS_WIN)
        if(!::InitializeCriticalSectionAndSpinCount(&mx, 4000))
            ::InitializeCriticalSection(&mx);
        return true;
#elif defined(NX_OS_LINUX)

#   ifndef PTHREAD_MUTEX_RECURSIVE
#   define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
#   endif

        int err = 0;
        pthread_mutexattr_t attr;
        err = pthread_mutexattr_init(&attr);
        if (err != 0) return false;
        err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        if (err != 0) return false;
        err = pthread_mutex_init(&mx, &attr);
        if (err != 0) return false;
        err = pthread_mutexattr_destroy(&attr);
        return (err == 0);

#endif
    }

    inline bool mutex_lock(mutex_t& mx)
    {
#if defined(NX_OS_WIN)
        ::EnterCriticalSection(&mx);
        return true;
#elif defined(NX_OS_LINUX)
        int err = pthread_mutex_lock(&mx);
        return (err == 0);
#endif
    }

    inline bool mutex_unlock(mutex_t& mx)
    {
#if defined(NX_OS_WIN)
        ::LeaveCriticalSection(&mx);
        return true;
#elif defined(NX_OS_LINUX)
        int err = pthread_mutex_unlock(&mx);
        return (err == 0);
#endif
    }

    inline bool mutex_trylock(mutex_t& mx)
    {
#if defined(NX_OS_WIN)
        return (!!::TryEnterCriticalSection(&mx));
#elif defined(NX_OS_LINUX)
        int err = pthread_mutex_trylock(&mx);
        return (err == 0);
#endif
    }

    inline bool mutex_destroy(mutex_t& mx)
    {
#if defined(NX_OS_WIN)
        ::DeleteCriticalSection(&mx);
        return true;
#elif defined(NX_OS_LINUX)
        int err = pthread_mutex_destroy(&mx);
        return (err == 0);
#endif
    }
}

//////////////////////////////////////////////////////////////////////////
NX_END
//////////////////////////////////////////////////////////////////////////
